package edu.cs.usask.ca.silver.plugin.rcp.handlers.create.helper;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Hashtable;
import java.util.Map;

import org.apache.log4j.Logger;
import org.eclipse.jface.dialogs.MessageDialog;

import edu.cs.usask.ca.silver.api.businesslogic.SilverServiceI;
import edu.cs.usask.ca.silver.api.persistence.StorageI;
import edu.cs.usask.ca.silver.businesslogic.ids.FileID;
import edu.cs.usask.ca.silver.businesslogic.ids.ModelProjectID;
import edu.cs.usask.ca.silver.businesslogic.ids.ModelVersionID;
import edu.cs.usask.ca.silver.businesslogic.ids.ParameterID;
import edu.cs.usask.ca.silver.businesslogic.ids.ScenarioCollectionID;
import edu.cs.usask.ca.silver.businesslogic.ids.ScenarioID;
import edu.cs.usask.ca.silver.businesslogic.ids.UserID;
import edu.cs.usask.ca.silver.exceptions.VensimLogicErrorException;
import edu.cs.usask.ca.silver.plugin.rcp.Activator;
import edu.cs.usask.ca.silver.plugin.rcp.components.SilverSystemMessageEnumerations;
import edu.cs.usask.ca.silver.plugin.rcp.dao.SilverBusinessLogicController;
import edu.cs.usask.ca.silver.plugin.rcp.dialogs.create.CreateGroupDialog;
import edu.cs.usask.ca.silver.plugin.rcp.dialogs.create.CreateModelProjectDialog;
import edu.cs.usask.ca.silver.plugin.rcp.dialogs.create.CreateModelVersionDialog;
import edu.cs.usask.ca.silver.plugin.rcp.dialogs.create.CreateScenarioCollectionDialog;
import edu.cs.usask.ca.silver.plugin.rcp.dialogs.create.CreateScenarioDialog;
import edu.cs.usask.ca.silver.plugin.rcp.jobs.LoggerReportScheduler;

public class CreateDataProcedures {

	private static Logger logger = Logger.getLogger(CreateDataProcedures.class);

	public static void createNewGroupFromCreateGroupDialog(
			CreateGroupDialog dialog, StorageI storage, UserID user) {
		try {
			SilverBusinessLogicController.borrowGroupObject().createGroup(
					dialog.getGroupName(), user, dialog.getGroupPrivacy(),
					dialog.getDescription(), storage);

			SilverBusinessLogicController.returnLastGroupObject();

		} catch (NullPointerException e1) {
			MessageDialog.openError(Activator.getDefault().getWorkbench()
					.getActiveWorkbenchWindow().getShell(),
					SilverSystemMessageEnumerations.SYSTEM_ERROR.getTitle(),
					SilverSystemMessageEnumerations.SYSTEM_ERROR.getErrorMsg());
		} catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,
					"error in CreateDataProcedures in RCP plugin", e);
		}
	}

	public static void createNewMpFromCreateMpDialogWithAGroupIdSelected(
			CreateModelProjectDialog createMpDialog, StorageI storage) {

		try {
			ModelProjectID projectID = SilverBusinessLogicController
					.borrowMpObject().createModelProject(
							createMpDialog.getSelectedModel(),
							createMpDialog.getmodelProjectParentGroup(),
							createMpDialog.getmodelProjectName(),
							createMpDialog.getmodelProjectCreator(),
							createMpDialog.getmodelProjectDescription(),
							createMpDialog.getSoureFilePath(),
							createMpDialog.getInputFilePath(), storage);

			FileID fileID = null;

			// Iterate through File List and Add Associate Files to Model
			// Project
			if (createMpDialog.getAssociateFileSelection()) {
				String associateFileLocation;
				File associateFile = null;
				for (int i = 0; i < createMpDialog.getAssociateFileArray().length; i++) {
					associateFileLocation = createMpDialog
							.getAssociateFileArray()[i];
					associateFile = new File(associateFileLocation);

					fileID = SilverBusinessLogicController
							.borrowFileObject()
							.createFile(
									associateFile.getName(),
									"Associate File for" + projectID.toString(),
									associateFile.getAbsolutePath(), storage);

					SilverBusinessLogicController.borrowFileObject()
							.addFileToModelProject(fileID, projectID, storage);

				}

			}
			SilverBusinessLogicController.returnAllMpObject();
			SilverBusinessLogicController.returnAllFileObject();
		} catch (java.lang.UnsatisfiedLinkError e1) {
			MessageDialog
					.openError(
							Activator.getDefault().getWorkbench()
									.getActiveWorkbenchWindow().getShell(),
							"Error creating a new Model Project",
							"SILVER could not process Vensim model at the moment;\nPlease load Vensim DLL in Preference first.");
		}

		catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,
					"error in CreateDataProcedures in RCP plugin", e);
		}
	}

	public static void createNewMvFromCreateMvDialogWithAModelProjectIdSelected(
			CreateModelVersionDialog dialog, StorageI storage) {
		try {
			ModelVersionID mvid = SilverBusinessLogicController
					.borrowMvObject().createGenericModelVersion(
							dialog.getmodelVersionName(),
							dialog.getmodelVersionCreator(),
							dialog.getmodelVersionDescription(),
							dialog.getParentModelProjectId(), storage);
			FileID fileID = null;

			// Iterate through File List and Add Associate Files to Model
			// Version
			if (dialog.getAssociateFileSelection()) {
				logger.info("adding associate files to " + mvid);
				String associateFileLocation;
				File associateFile = null;
				for (int i = 0; i < dialog.getAssociateFileArray().length; i++) {
					associateFileLocation = dialog.getAssociateFileArray()[i];
					associateFile = new File(associateFileLocation);

					fileID = SilverBusinessLogicController.borrowFileObject()
							.createFile(associateFile.getName(),
									"Associate File for" + mvid.toString(),
									associateFile.getAbsolutePath(), storage);

					SilverBusinessLogicController.borrowFileObject()
							.addFileToModelVersion(fileID, mvid, storage);

				}

			}
			SilverBusinessLogicController.returnAllFileObject();
			SilverBusinessLogicController.returnAllMvObject();

		} catch (java.lang.UnsatisfiedLinkError e1) {
			MessageDialog
					.openError(
							Activator.getDefault().getWorkbench()
									.getActiveWorkbenchWindow().getShell(),
							"Error creating a new Model Project",
							"SILVER could not process Vensim model at the moment;\nPlease load Vensim DLL in Preference first.");
		} catch (Exception e) {
			// TODO: handle exception
			LoggerReportScheduler.scheduleErrorLogging(logger,
					"error in CreateDataProcedures in RCP plugin", e);
		}
	}

	// borrows ScenarioCollection
	public static void createNewScFromCreateScDialogwithAModelVersionIdSelected(
			CreateScenarioCollectionDialog dialog, StorageI storage) {
		try {
			ScenarioCollectionID scid = SilverBusinessLogicController
					.borrowScObject().createScenarioCollection(
							dialog.getscenarioCollectionName(),
							dialog.getscenarioCollectionCreator(),
							dialog.getscenarioCollectionDescription(),
							dialog.getParentModelVersionID(), storage);

			CreateScenariosInThisScenarioCollectionFromTrajectoryFiles(scid,
					dialog, storage);

			if (dialog.getAreAssociateFilesRequested()) {
				FileID fileID;
				logger.info("adding associate files to " + scid);

				File associateFile = null;
				for (String associateFileLocation : dialog
						.getAssociateFileArray()) {
					associateFile = new File(associateFileLocation);
					fileID = SilverBusinessLogicController.borrowFileObject()
							.createFile(associateFile.getName(),
									"Associate File for" + scid.toString(),
									associateFile.getAbsolutePath(), storage);

					SilverBusinessLogicController.borrowFileObject()
							.addFileToScenarioCollection(fileID, scid, storage);

				}

			}
			SilverBusinessLogicController.returnAllFileObject();
			SilverBusinessLogicController.returnAllScObject();
		} catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,
					"error in CreateDataProcedures in RCP plugin", e);
		}
	}

	// borrows Mv, Parameter,
	private static void CreateScenariosInThisScenarioCollectionFromTrajectoryFiles(
			ScenarioCollectionID scid, CreateScenarioCollectionDialog dialog,
			StorageI storage) throws Exception {

		// get the baseline scenario associated with this model version

		ScenarioID baseScenarioId = SilverBusinessLogicController
				.borrowMvObject().getBaselineScenarioId(
						dialog.getParentModelVersionID(), storage);

		// no baseline scenario associated for ModelVersion, return and stop
		// creating scenarios.
		if (baseScenarioId == null) {
			MessageDialog
					.openError(
							Activator.getDefault().getWorkbench()
									.getActiveWorkbenchWindow().getShell(),
							"Can not reproduce scenario from Vensim trajectory file.",
							"SILVER could not process vpm file at the moment;\n\nTo successfully import scenario from vdf files, please create a ModelVersion with Vensim source file, or point a scenario as baseline first.");
			return;
		}
		// get the baseline parameter ids associated with this model version

		ParameterID[] baseParameterIds = SilverBusinessLogicController
				.borrowScenario().getParameterIds(baseScenarioId, storage);

		SilverBusinessLogicController.returnLastScenarioObject();
		SilverBusinessLogicController.returnLastMvObject();

		for (String strTrajectoryFile : dialog
				.getArrayStrTrajectoryFilesForReconstructingScenarios())
			CreateVensimScenarioInThisScenarioCollectionFromTrajectoryFile(
					scid, dialog.getscenarioCollectionCreator(),
					dialog.getParentModelVersionID(), strTrajectoryFile,
					baseScenarioId, baseParameterIds, storage);

	}

	// borrows silverservice
	private static ScenarioID CreateVensimScenarioInThisScenarioCollectionFromTrajectoryFile(
			ScenarioCollectionID scenarioCollectionId, String strCreator,
			ModelVersionID curModelVersionId, String strTrajectoryFile,
			ScenarioID templateScenarioId, ParameterID[] baseParameterIds,
			StorageI storage) {

		File fileTrajectory = new File(strTrajectoryFile);
		try {
			Map<Integer, Double> map = SilverBusinessLogicController
					.borroweSilverServiceObject()
					.ExtractValuesForParametersFromVDFTrajectoryFile(
							templateScenarioId, fileTrajectory, storage);
			Hashtable<String, String>[] arrayHashMapStrParameterAttributesToStrValue = MapStrParameterAttributesToStrValueFromMapParameterIdToDouble(
					map, storage);

			String strScenarioName = StrDefaultScenarioNameForScenarioReconstructedFromTrajectoryFileName(strTrajectoryFile);
			String strScenarioDescription = StrDefaultScenarioDescriptionForScenarioReconstructedFromTrajectoryFileName(strTrajectoryFile);
			return AddVensimScenario(strScenarioName, strCreator,
					strScenarioDescription, curModelVersionId,
					scenarioCollectionId, new String[] { strTrajectoryFile },
					arrayHashMapStrParameterAttributesToStrValue,
					baseParameterIds, storage);
		} catch (VensimLogicErrorException e1) {
			LoggerReportScheduler.scheduleErrorLogging(logger,
					"error in Carrying out Vensim Commands in RCP plugin", e1);
		} catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,
					"error in CreateDataProcedures in RCP plugin", e);
			System.out
					.println("Problem in extracting values from trajectory file "
							+ fileTrajectory.toString()
							+ ".  continuing on to the next trajectory file to be added");
		}

		return null;
	}

	private static String StrDefaultScenarioNameForScenarioReconstructedFromTrajectoryFileName(
			String strTrajectoryFile) {
		return "Scenario for " + strTrajectoryFile;
	}

	private static String StrDefaultScenarioDescriptionForScenarioReconstructedFromTrajectoryFileName(
			String strTrajectoryFile) {
		return "Scenario reconstructed from file " + strTrajectoryFile;
	}

	@SuppressWarnings("unchecked")
	private static Hashtable<String, String>[] MapStrParameterAttributesToStrValueFromMapParameterIdToDouble(
			Map<Integer, Double> mapParameterIdToValue, StorageI storage)
			throws Exception {
		// create an equivalent map from Strings to Strings.
		// the value part is easy -- we can render the doubles as Strings

		// to get the keys, we need to map the parameter ids into their names

		java.util.Set<Integer> setParameterIdsAsIntegers = mapParameterIdToValue
				.keySet();

		// we have to work around type-erasure's problems with allocating an
		// array of generics
		Hashtable<String, String> arrayHT[];

		// TODO: See if can get rid of this technique to get the associated
		// class
		Hashtable<String, String> htTmp = new Hashtable<String, String>();

		// ok, we can allocate a type-safe array here
		arrayHT = (Hashtable<String, String>[]) Array.newInstance(
				htTmp.getClass(), setParameterIdsAsIntegers.size());

		int iParameter = 0;
		for (Integer integerParameterId : setParameterIdsAsIntegers) {
			assert integerParameterId != null : "Unexpectedly got null parameter id in MapStrParameterNameToStrValueFromMapParameterIdToDouble";
			ParameterID parameterId = new ParameterID(integerParameterId);

			// TODO: ***Passing in an array of hashtables to specify a few known
			// items is pretty ugly. We should really
			// have a well-defined interface that is type-checked

			String strParameterName = parameterId
					.getEntryNameFromDatabase(storage);
			// String strOriginalCommentFromBaseline =
			// SilverAdapterFactory.getInstance().getParameterComment(scenarioId,
			// parameterId);
			String strValue = mapParameterIdToValue.get(integerParameterId)
					.toString();
			String strComment = ""; // the default value could be overridden in
									// the vdf we are reading, and in general,
									// we won't know where it came from. Make
									// the default comment empty.

			// create a new hash table specific for this parameter index
			arrayHT[iParameter] = new Hashtable<String, String>();
			// associate it with the needed information -- this should really be
			// cleaned up
			arrayHT[iParameter].put("Name", strParameterName);
			arrayHT[iParameter].put("Value", strValue);
			arrayHT[iParameter].put("Comment", strComment);

			// ok, now we prepare for the next parameter
			iParameter++;
		}

		return arrayHT;
	}

	// We'd ideally like to return a Map<ParameterID, Double> , but this was
	// causing a bizarre error
	// where the hash table wasn't working (see
	// CreateScenarioTableContentProvider)
	// retrieve the values for parameters from a VDF file. This simply delegates
	// to the SILVER service
	public Map<Integer, Double> ExtractValuesForParametersFromVDFTrajectoryFile(
			ScenarioID scenarioId, File addFile, StorageI storage)
			throws Exception {
		Map<Integer, Double> result = null;
		try {
			SilverServiceI ss = SilverBusinessLogicController
					.borroweSilverServiceObject();
			result = ss.ExtractValuesForParametersFromVDFTrajectoryFile(
					scenarioId, addFile, storage);
			SilverBusinessLogicController.returnAllSilverServiceObject();
		} catch (java.lang.UnsatisfiedLinkError e1) {
			MessageDialog
					.openError(
							Activator.getDefault().getWorkbench()
									.getActiveWorkbenchWindow().getShell(),
							"Error creating a new Model Project",
							"SILVER could not process Vensim model at the moment;\nPlease load Vensim DLL in Preference first.");
			throw e1;
		} catch (Exception e) {
			throw e;
		}
		return result;
	}

	public static void createNewScenarioFromCreateScenarioDialogWithAScenarioCollectionIdSelected(
			CreateScenarioDialog dialog, StorageI storage) {
		try {

			ScenarioID sid = SilverBusinessLogicController.borrowScenario()
					.createGenericScenarioAndParameterSet(
							dialog.getScenarioName(),
							dialog.getScenarioCreator(),
							dialog.getScenarioDescription(),
							dialog.getModelVersionID(),
							dialog.getScenarioCollectionID(), storage);

			if (dialog.getAssociateFileSelection()) {
				FileID fileID;
				logger.info("adding associate files to " + sid);
				String associateFileLocation;
				File associateFile = null;
				for (int i = 0; i < dialog.getAssociateFileArray().length; i++) {
					associateFileLocation = dialog.getAssociateFileArray()[i];
					associateFile = new File(associateFileLocation);

					fileID = SilverBusinessLogicController.borrowFileObject()
							.createFile(associateFile.getName(),
									"Associate File for" + sid.toString(),
									associateFile.getAbsolutePath(), storage);

					SilverBusinessLogicController.borrowFileObject()
							.addFileToScenario(fileID, sid, storage);

				}

			}
			SilverBusinessLogicController.returnAllScenarioObject();
			SilverBusinessLogicController.returnAllFileObject();
		} catch (Exception e) {
			// TODO: handle exception
			LoggerReportScheduler.scheduleErrorLogging(logger,
					"error in CreateDataProcedures in RCP plugin", e);
		}
	}

	public static ScenarioID AddVensimScenario(
			String strScenarioName,
			String strScenarioCreator,
			String strScenarioDescription,
			ModelVersionID modelVersionID, // the associated model
			ScenarioCollectionID scenarioCollectionId,
			String[] arrayStrAssociatedFiles,
			Hashtable<String, String>[] arrayMpParameterNameToStrParameterValue,
			ParameterID[] arrayParamId, StorageI storage) throws Exception {
		try {
			// ok, create the scenario, using the basic information
			ScenarioID scenarioID = SilverBusinessLogicController
					.borrowScenario().createVensimScenarioAndParameterSet(
							strScenarioName, strScenarioCreator,
							strScenarioDescription, modelVersionID, // the
																	// associated
																	// model
							scenarioCollectionId, storage); // the enclosing
															// scenario
			// collection
			// ok, now add in parameters

			AddParameters(scenarioID, arrayMpParameterNameToStrParameterValue,
					arrayParamId, storage);

			AddAssociatedFiles(scenarioID, arrayStrAssociatedFiles, storage);

			SilverBusinessLogicController.returnAllParameterObject();
			SilverBusinessLogicController.returnAllFileObject();
			SilverBusinessLogicController.returnLastScenarioObject();
			return scenarioID;
		} catch (Exception e) {
			LoggerReportScheduler.scheduleErrorLogging(logger,
					"error in CreateDataProcedures in RCP plugin", e);

			throw new RuntimeException("Unable to add scenario with name "
					+ strScenarioName);
		}
	}

	// no need to handle object pooling
	private static void AddAssociatedFiles(ScenarioID scenarioID,
			String[] arrayStrAssociatedFiles, StorageI storage)
			throws IOException {

		if (arrayStrAssociatedFiles != null) {
			// Iterate through File List and Add Associated Files to Scenario
			logger.info("Adding associated files to" + scenarioID);

			for (int j = 0; j < arrayStrAssociatedFiles.length; j++)
				AddAssociatedFile(scenarioID, arrayStrAssociatedFiles[j],
						storage);

		}
	}

	// no need to handle object pooling
	private static void AddAssociatedFile(ScenarioID scenarioID,
			String strAssociateFileLocation, StorageI storage)
			throws IOException {
		File associateFile = new File(strAssociateFileLocation);

		if (!associateFile.canRead()) {
			throw new IOException(
					"Attempting to parse source file failed. Could not open: "
							+ strAssociateFileLocation);
		}

		try {
			// create the file information in the database
			FileID fileID = SilverBusinessLogicController.borrowFileObject()
					.createFile(associateFile.getName(),
							"Associated File for" + scenarioID.toString(),
							associateFile.getAbsolutePath(), storage);

			// associate the file with the scenario

			SilverBusinessLogicController.borrowFileObject().addFileToScenario(
					fileID, scenarioID, storage);
		} catch (Exception e) {
			logger.info("Unabled to add associated file "
					+ strAssociateFileLocation
					+ ".  Please add manually.  Continuing on to the next file.");
			LoggerReportScheduler.scheduleErrorLogging(logger,
					"error in CreateDataProcedures in RCP plugin", e);

		}

	}

	// no need to handle object pooling
	private static void AddParameters(ScenarioID scenarioID,
			Hashtable<String, String>[] parameterData, ParameterID[] paramId,
			StorageI storage) {
		for (int i = 0; i < parameterData.length; i++) {
			try {
				SilverBusinessLogicController.borrowParameterObject()
						.addParamteterToScenario(paramId[i], scenarioID,
								parameterData[i].get("Value"),
								parameterData[i].get("Comment"), storage);
			} catch (Exception e) {
				LoggerReportScheduler.scheduleErrorLogging(logger,
						"error in CreateDataProcedures in RCP plugin", e);
			}
		}

	}
}
